package org.dreamwork.android.websocket;

import android.os.Binder;
import android.os.Looper;
import android.util.Log;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * <p>Created by seth.yang on 2017/3/4.
 * <p>该类企图提供一个统一的，可在 Android 应用中使用的 WebSocket 客户端，使用时，通过 <code>Service</code>的 <code>bind</code>
 * 方法返回该类的实例。
 * <p><i>注意，若使用 <code>IntentService</code>，必须在重写的 <code>onBind</code> 方法内手动启动 Service
 * (通过调用 <code>.startService</code> 方法)</i>
 * <p>该类的任何实现子类，必须覆盖 {@link #cast(Object) byte[] cast (T message)} 或 {@link #asString(Object) String asString (T message)}
 * 中的至少一个。这个由{@link #AbstractWebSocketClientBinder(String, MessageType, Implementation) 构造函数}传入的
 * {@link org.dreamwork.android.websocket.IWebSocketBinder.MessageType type} 参数决定</p>
 * <p>一个典型的 <code>IntentService</code> 实现看起来如下：
 * <pre>
 *     public WebSocketClientIntentService extends
 *         private static final String URL = "ws://example.server.com/websocket/";
 *         private AbstractWebSocketClientBinder&lt;String&gt; binder
 *               = new AbstractWebSocketClientBinder&lt;String&gt; (URL, MessageType.ASCII, Implementation.OK_HTTP_3_6_0) {
 *             &#64;Override
 *             public String asString (String message) {
 *                 return message;
 *             }
 *         };
 *
 *         ...
 *         &#64;Override
 *         public IBinder onBind (Intent intent) {
 *             return binder;
 *         }
 *
 *         &#64;Override
 *         protected void onHandleIntent (Intent intent) {
 *             // do something
 *             try {
 *                 binder.loop ();
 *             } catch (Exception ex) {
 *                 Log.w (TAG, ex.getMessage (), ex);
 *             }
 *         }
 *     }
 * </pre>
 * </p>
 * 请务必不要在主线程中执行 <code>loop</code> 方法，这将阻塞主线程，从而导致 Android <code>ANR</code> 错误！
 */

public abstract class AbstractWebSocketClientBinder<T> extends Binder implements IWebSocketBinder<T>, IWebSocketListener  {
    private static final String TAG = "WSBinder";

    private BlockingQueue<Object> queue   = new LinkedBlockingQueue<> ();
    private String                url;
    private MessageType           type;
    private Object                QUIT    = new byte[0];
    private boolean               running = true, connected;
    private Implementation        impl;

    protected AbstractWebSocketClientBinder (String url, MessageType type, Implementation impl) {
        this.url  = url;
        this.type = type;
        this.impl = impl;
    }

    @Override
    public MessageType getMessageType () {
        return type;
    }

    @Override
    public byte[] cast (T message) {
        return null;
    }

    @Override
    public String asString (T message) {
        return null;
    }

    @Override
    public void send (T message) {
        if (!queue.offer (message)) {
            throw new IllegalStateException ();
        }
    }

    @Override
    public void onOpen () {}

    @Override
    public void onMessageReceived (String raw) {}

    @Override
    public void onMessageReceived (byte[] buff) {}

    @Override
    public void onClose () {}

    @Override
    public void onError (Throwable error) {
        Log.w (TAG, error.getMessage (), error);
        abort ();
    }

    public boolean isConnected () {
        return connected;
    }

    /**
     * <p>主循环，用于监听服务器端消息，及发送消息</p>
     * <p>请保证该方法不在主线程内被调用</p>
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public void loop () throws ExecutionException, InterruptedException {
        if (Looper.getMainLooper () == Looper.myLooper ()) {
            throw new IllegalThreadStateException ("Can't invoke in main loop.");
        }

        Log.d (TAG, "connecting to " + url);
        IWebSocketLibrary library = WebSocketLibraryFactory.create (this, impl);
        try {
            library.connect (url);
            connected = true;
            Log.d (TAG, "connected. entering the WS LOOP.");

            while (running) {
                try {
                    Object o = queue.take ();
                    if (o == QUIT) {
                        break;
                    }

                    @SuppressWarnings ("unchecked")
                    T message = (T) o;
                    if (getMessageType () == IWebSocketBinder.MessageType.ASCII) {
                        library.send (asString (message));
                    } else if (getMessageType () == IWebSocketBinder.MessageType.Binary) {
                        library.send (cast (message));
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
            }
        } catch (Exception ex) {
            Log.w (TAG, ex.getMessage (), ex);
        } finally {
            if (library != null) {
                library.close ();
            }
        }
    }

    public void abort () {
        if (!queue.isEmpty ()) {
            queue.clear ();
        }
        running = false;
        queue.offer (QUIT);
    }
}